package com.lu.netty.io.client

import androidx.annotation.IntRange
import com.lu.netty.io.core.EventType
import com.lu.netty.io.core.IoConnector
import com.lu.netty.io.core.IoState
import com.lu.netty.io.core.NettyConfigure
import com.lu.netty.io.core.NettyConnector
import com.lu.netty.io.core.NettyLog
import io.netty.bootstrap.Bootstrap
import io.netty.channel.Channel
import io.netty.channel.ChannelFuture
import io.netty.channel.ChannelFutureListener
import io.netty.channel.ChannelOption
import io.netty.channel.nio.NioEventLoopGroup
import io.netty.channel.socket.nio.NioSocketChannel
import java.io.IOException
import java.net.InetSocketAddress
import java.util.concurrent.TimeUnit

/**
 * File Name :NettyClient
 * Author : luqihua
 * Date : 2024/4/12
 * Desc :
 **/

abstract class ANettyClient(
    private val ip: String,
    @IntRange(from = 1000, to = 65535)
    private val port: Int,
) : IoConnector(), Runnable, IClientAdapterCallback {

    companion object {
        private const val TAG = "NettyClient"
    }

    private var connector: NettyConnector? = null
    private val address = InetSocketAddress(ip, port)

    //事件循环组
    private var bootstrap = Bootstrap()

    //线程池
    private var loopGroup = NioEventLoopGroup()

    init {
        reset()
    }

    private fun reset() {
        setIoState(IoState.INIT)
        retryTimes = 0
        bootstrap = Bootstrap()
        loopGroup = NioEventLoopGroup()
        bootstrap.group(loopGroup)
            .channel(NioSocketChannel::class.java)
            .handler(ClientChannelHandler(this))
            .option(ChannelOption.SO_RCVBUF, NettyConfigure.bufferSize)
            .option(ChannelOption.SO_SNDBUF, NettyConfigure.bufferSize)
            .option(ChannelOption.TCP_NODELAY, true)
            .option(ChannelOption.SO_KEEPALIVE, true)
            .option(ChannelOption.SO_TIMEOUT, NettyConfigure.soTimeOut)
    }

    override fun run() {
        if (!checkCanConnect()) {
            return
        }
        reset()
        setIoState(IoState.CONNECTING)
        retryTimes++
        NettyLog.d(
            TAG,
            "start Connect times=$retryTimes thread=${Thread.currentThread().name}"
        )
        try {
            val channelFuture = bootstrap.connect(address)
            channelFuture.addListener(object : ChannelFutureListener {
                override fun operationComplete(future: ChannelFuture) {
                    if (!future.isSuccess) {
                        setIoState(IoState.INIT)
                        if (canRetry()) {
                            future.channel().eventLoop().schedule({
                                NettyLog.w(
                                    TAG,
                                    "重连服务器.....",
                                    channelFuture.cause()
                                )
                                run()
                                future.channel().close()
                            }, NettyConfigure.reconnectDelay, TimeUnit.MILLISECONDS)
                        } else {
                            setIoState(IoState.DISCONNECT)
                        }
                    }
                }
            })
        } catch (e: IOException) {
            setIoState(IoState.INIT)
            close()
            NettyLog.e(TAG, "client error", e)
        }
    }

    override fun onEvent(channel: Channel, type: EventType) {
        this.connector = NettyConnector(channel)
        when (type) {
            is EventType.DISCONNECTED -> {
                setIoState(IoState.DISCONNECT)
            }

            is EventType.CONNECTED -> {
                setIoState(IoState.CONNECTED)
            }

            is EventType.Error -> {
                setIoState(IoState.ERROR)
            }
        }
    }


    override fun start() {
        NettyConfigure.executor.execute(this)
    }

    override fun close() {
        NettyLog.d(TAG, "close: ")
        if (getIoState() < IoState.CLOSE) {
            setIoState(IoState.CLOSE)
            connector?.close()
        }
    }

    override fun release() {
        if (getIoState() != IoState.RELEASE) {
            setIoState(IoState.RELEASE)
            try {
                connector?.close()
                loopGroup.shutdownGracefully()
            } catch (e: Exception) {
                e.printStackTrace()
            }
            NettyLog.e(TAG, "client release")
        }
    }

    open fun send(message: String) {
        if (getIoState() == IoState.CONNECTED) {
            connector?.send(message)
        }
    }
}